package uk.ac.aber.paws.server.core;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Calendar;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import static uk.ac.aber.paws.server.config.Constants.*;
import uk.ac.aber.paws.server.config.ConfigManager;
import uk.ac.aber.paws.server.core.RunJob;
import uk.ac.aber.paws.server.db.DBLocalCallQueue;
import uk.ac.aber.paws.server.db.DBRemoteCallQueue;
import uk.ac.aber.paws.server.security.SSLInitialise;

/**
 * @author Rob Johnson, Aberystwyth University
 * @email rob.johnson@aber.ac.uk
 * @date 21st May 2012
 * 
 * PAWSServer.java
 * 
 * This is the main class for the PAWS server wrapper application.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
public class PAWSServer  {
	
	private static RunJob sleepMonitorThread;
	private static RunJob secureServerListener;
	private static RunJob managementServerListener;
	private static RunJob statsArchiverThread;
	private static RunJob todaysStatsThread;
	private static RunJob statsPageThread;
	private static RunJob orgSavingsRefresherThread;
	private static RunJob purgeThread;
	
	private static DBRemoteCallQueue DBRemoteCalls;
	private static DBLocalCallQueue DBLocalCalls;
	public static Logger LOG_FILE;
	
	public static void main(String args[]) {
		
		if (args.length == 2) {

			// load in the configuration file
			ConfigManager.loadProperties(args[1],false);

			// start the logging based off the log level specified in args[0]			
			String logFile = ConfigManager.getProperty("app.main.homedir")+ConfigManager.getProperty("app.main.logdir")+ConfigManager.getProperty("app.main.logfilename");
			LOG_FILE = Logging.startLogFile(args[0],logFile);
			
			PAWSServer.LOG_FILE.log(Level.ALL,"Running PAWS Server Version: "+VERSION);
			
			// import SSL certs from local domain controllers so that LDAPS lookups can be made to DC
			importSSLCerts();
			
			// initialise the DB logging queues
			initDBQueues();
			
			// initialise the jobs that perform stats archiving within the database
			initStatsArchivingJobs();
			
			// initialise the jobs that perform maintenance on the database
			initDBCleanup();
		
			// initialise the thread that deals with all processing and sleeping of client computers
			initSleepingProcedures();
			
			// initialises the secure server socket for all aspects of communication for the server agent
			initNetworking();	
		}
	}
	
	/**
	 * Returns reference to the Local DBCall queue
	 * @return
	 */
	public static DBLocalCallQueue getDBLocalCalls() {
		return DBLocalCalls;
	}
	
	/**
	 * Returns reference to the Remote DBCall queue
	 * @return
	 */
	public static DBRemoteCallQueue getDBRemoteCalls() {
		return DBRemoteCalls;
	}
	
	/**
	 * Initialises the database queues for both local and remote databases
	 */
	private static void initDBQueues() {
		
		// create the database queues for spooling database inserts
		DBRemoteCalls = new DBRemoteCallQueue();
		PAWSServer.LOG_FILE.log(Level.ALL,"DBRemoteCalls creation: "+DBRemoteCalls);
		DBLocalCalls = new DBLocalCallQueue();
		PAWSServer.LOG_FILE.log(Level.ALL,"DBLocalCalls creation: "+DBLocalCalls);
	}
	
	/**
	 * Initialises the job that archives stats within the database
	 */
	private static void initStatsArchivingJobs() {
			
		// calculate and archive combined savings stats for required period
		if (ConfigManager.getProperty("app.options.statsarchiver") != null) {
			if (Integer.parseInt(ConfigManager.getProperty("app.options.statsarchiver")) == 1) {
				PAWSServer.LOG_FILE.log(Level.ALL,"Stats Archiver is enabled.");
				statsArchiverThread = actionThread(true,statsArchiverThread,RUNJOB_CHECK_ARCHIVE_STATS,"app.main.processinterrupts.statsarchive");

			}
			else {
				PAWSServer.LOG_FILE.log(Level.ALL,"Stats Archiver is disabled.");
			}
		}
		else {
			PAWSServer.LOG_FILE.log(Level.ALL,"app.options.statsarchiver config not specified");
		}
		
		// calculate the organisation savings totals
		if (ConfigManager.getProperty("app.options.orgsavings") != null) {
			if (Integer.parseInt(ConfigManager.getProperty("app.options.orgsavings")) == 1) {
				PAWSServer.LOG_FILE.log(Level.ALL,"Org Savings Refresher is enabled.");
				orgSavingsRefresherThread = actionThread(true,orgSavingsRefresherThread,RUNJOB_CHECK_ORGSAVINGS_REFRESH,"app.main.processinterrupts.orgsavings");

			}
			else {
				PAWSServer.LOG_FILE.log(Level.ALL,"Org Savings Refresher is disabled.");
			}
		}
		else {
			PAWSServer.LOG_FILE.log(Level.ALL,"app.options.statsarchiver config not specified");
		}

		// calculate the savings stats for just today
		if (ConfigManager.getProperty("app.options.livestats") != null) {
			
			if (Integer.parseInt(ConfigManager.getProperty("app.options.livestats")) == 1) {
				PAWSServer.LOG_FILE.log(Level.ALL,"Live Stats is enabled.");
				// update the stats for live up-to-date stats for the current day
				todaysStatsThread = actionThread(true,todaysStatsThread,RUNJOB_UPDATE_TODAY_ARCHIVE_STATS,"app.main.processinterrupts.todaystatsupdate");
			}
			else {
				PAWSServer.LOG_FILE.log(Level.ALL,"Live Stats is disabled.");
			}
		}
		else {
			PAWSServer.LOG_FILE.log(Level.ALL,"app.options.livestats config not specified");
		}
	}
	
	/**
	 * Performs maintenance operations on the database such as event log purging.
	 */
	private static void initDBCleanup() {
		
		if (ConfigManager.getProperty("app.options.purge") != null) {
			if (Integer.parseInt(ConfigManager.getProperty("app.options.purge")) == 1) {
				PAWSServer.LOG_FILE.log(Level.ALL,"DB Purger is enabled.");
				purgeThread = actionThread(true,purgeThread,RUNJOB_CHECK_PURGE,"app.main.processinterrupts.purge");

			}
			else {
				PAWSServer.LOG_FILE.log(Level.ALL,"DB Purger is disabled.");
			}
		}
		else {
			PAWSServer.LOG_FILE.log(Level.ALL,"app.options.statsarchiver config not specified");
		}
		
	}
	
	/**
	 * Starts the job that processes all sleep profiles and instructs client computers to sleep accordingly.
	 */
	private static void initSleepingProcedures() {
		
		if (ConfigManager.getProperty("app.options.monitorsleepprofiles") != null) {
			if (Integer.parseInt(ConfigManager.getProperty("app.options.monitorsleepprofiles")) == 1) {
				PAWSServer.LOG_FILE.log(Level.ALL,"Monitor Sleep Profiles is enabled.");
				
				// monitor all sleep profiles
				PAWSServer.LOG_FILE.log(Level.ALL,"Starting sleep monitor: "+sleepMonitorThread);
				sleepMonitorThread = actionThread(true,sleepMonitorThread,RUNJOB_PROCESS_PROFILES,"app.main.processinterrupts.sleepmonitor");
				
			}
			else {
				PAWSServer.LOG_FILE.log(Level.ALL,"Monitor Sleep Profiles is disabled.");
			}
		}
		else {
			PAWSServer.LOG_FILE.log(Level.ALL,"app.options.monitorsleepprofiles config not specified");
		}
	}
	
	/**
	 * Initialise the networking for the server socket, the main communication channel.
	 */
	private static void initNetworking() {
		
		// start the server socket listeners
		PAWSServer.LOG_FILE.log(Level.ALL,"Socket listener starting...");
		secureServerListener = actionThread(true,secureServerListener,RUNJOB_START_SECURESERVERSOCKET_LISTENER,null);
		
		managementServerListener = actionThread(true,managementServerListener,RUNJOB_START_MANAGEMENT_LISTENER,null);
		
		
		
		if (ConfigManager.getProperty("app.options.statspagegenerator") != null) {
			if (Integer.parseInt(ConfigManager.getProperty("app.options.statspagegenerator")) == 1) {
				PAWSServer.LOG_FILE.log(Level.ALL,"Stats Page Generator is enabled.");
				// job that generates the PSV status page on IS web server
				statsPageThread = actionThread(true,statsPageThread,RUNJOB_GENERATE_PSV_STATS,"app.main.processinterrupts.statsgeneration");
				
			}
			else {
				PAWSServer.LOG_FILE.log(Level.ALL,"Stats Page Generator is disabled.");
			}
		}
		else {
			PAWSServer.LOG_FILE.log(Level.ALL,"app.options.staspagegenerator config not specified");
		}
	}
	
	/**
	 * Imports the SSL certs from the local DCs so that LDAPS queries can be made.
	 */
	private static void importSSLCerts() {
		
		final StringTokenizer domainList = new StringTokenizer(ConfigManager.getProperty("app.net.domains"),",");
		
		while (domainList.hasMoreElements()) {
			
			AccessController.doPrivileged(new PrivilegedAction<Object>() {
				
				public Object run() {
			
					String domain = (String)domainList.nextElement();
					SSLInitialise.retrieveLDAPServerCerts(domain,Integer.parseInt(ConfigManager.getProperty("app.ldap.port."+domain)),"changeit");	
					return null;
				}
				
			});
		}
		
	}
	
	/**
	 * This procedure kicks off or kills the required thread
	 * @param Specify true to create the thread
	 * @param The reference to the thread - mainly used in the kill functionality
	 * @param The key identifier to be used by RunJob in order to identify the job to be created
	 * @param The key string to the configuration for identifying the thread sleep period between job execution
	 * @return The reference to the newly created RunJob object for when new Threads are created
	 */
	private static RunJob actionThread(boolean state, RunJob thread, int key, String sleepKey) {
		
		if (state) {
			
			// if a once only job
			if (sleepKey == null) {
				thread = new RunJob(key,-1);
			}
			else if (ConfigManager.getProperty(sleepKey).startsWith("time-")) {
				
				String keyVal = ConfigManager.getProperty(sleepKey);
				
				keyVal = keyVal.replaceAll("time-","");
				StringTokenizer st = new StringTokenizer(keyVal,":");
				
				int hour = 0, minute = 0;
				
				if (st.countTokens() == 2) {
					hour = Integer.parseInt((String)st.nextElement());
					minute = Integer.parseInt((String)st.nextElement());
				}
				
				Calendar cal = Calendar.getInstance();
				cal.set(Calendar.HOUR_OF_DAY,hour);
				cal.set(Calendar.MINUTE,minute);
				
				thread = new RunJob(key,60,cal);
			}
			// a recurring job
			else {
				thread = new RunJob(key,Integer.parseInt(ConfigManager.getProperty(sleepKey)));
			}
		}
		else {
			thread.interrupt();
		}
		
		return thread;
	}
}


